<?php
/**
 * @package Curly
 * @subpackage Cookies
 * @version 0.9
 * @link http://curly.codeplex.com/
 * @license http://curly.codeplex.com/license The MIT License
 * @author Dawid Zawada
 */

namespace Curly\Cookies
{
    /**
     * Provides a container for instances of the Cookie class
     * 
     * The class also allows you to set the file path of the cookie
     * jar file. 
     * 
     * @package Curly
 	 * @subpackage Cookies
     * @license http://curly.codeplex.com/license The MIT License
     */
    class CookieContainer implements \Countable, \IteratorAggregate
    {
        /**
         * Collection of cookies
         * 
         * @var array
         */
        protected $cookies = array();
        
        /**
         * Cookie jar file path
         * 
         * @var string
         */
        protected $jarFilePath;
        
        /**
         * Indication of whether the cookie jar file should
         * be created, even if the cookie jar file path is not specified
         * 
         * @var bool
         */
        protected $autoJarFileCreation = true;
        
        /**
         * Returns the cookie jar file path
         *
         * @return string
         */
        public function getJarFilePath()
        {
            return $this->jarFilePath;
        }
        
        /**
         * Sets the cookie jar file path
         * 
         * @param string $jarFilePath
         */
        public function setJarFilePath( $jarFilePath )
        {
            $this->jarFilePath = (string)$jarFilePath;
        }
        
        /**
         * Indicates whether the cookie jar file path
         * has been specified
         *
         * @return bool
         */
        public function hasJarFilePath()
        {
            return !empty( $this->jarFilePath );
        }
        
        /**
         * Indicates whether the cookie jar file should
         * be created, even if the cookie jar file path is not specified
         *
         * @return bool
         */
        public function isAutoJarFileCreation()
        {
            return $this->autoJarFileCreation;
        }
        
        /**
         * Sets indication of whether the cookie jar file should
         * be created even if not specified
         * 
         * @param bool $autoJarFileCreation
         */
        public function setAutoJarFileCreation( $autoJarFileCreation )
        {
            $this->autoJarFileCreation = (bool)$autoJarFileCreation;
        }
        
        /**
         * Adds the speicifed Cookie class instance to the container
         * 
         * If a cookie with the same name, path and domain already exists 
         * in the container it will be replaced by the one passed as a parameter
         * 
         * @param Curly\Cookies\Cookie $cookie
         */
        public function add( Cookie $cookie )
        {
            $key = $this->findStoredEqualCookie( $cookie );
            if( $key !== false )
            {
                $this->cookies[$key] = $cookie;
            }
            else
            {
                $this->cookies[] = $cookie;
            }
        }
        
        /**
         * Searches the container for a cookie with the same name, path and domain
         * that the cookie passed as a parameter has and returns the corresponding key
         * 
         * The method returns the associated key if the cookie is found, false otherwise
         * 
         * @param Curly\Cookies\Cookie $cookie
         * @return mixed 
         */
        protected function findStoredEqualCookie( Cookie $cookie )
        {
            foreach( $this->cookies as $key => $storedCookie )
            {
                if( $cookie->equals( $storedCookie ) )
                {
                    return $key;
                }
            }
            
            return false;
        }
        
        /**
         * Adds multiple Cookie class instances to the container
         * 
         * $cookies param must be either an array of Cookie instances 
         * or a CookieContainer instance
         * 
         * @param mixed $cookies
         * @throws \UnexpectedValueException
         * @throws \InvalidArgumentException
         */
        public function addMultiple( $cookies )
        {
            if( $cookies instanceof CookieContainer )
            {
                $this->addMultiple( $cookies->getCookies() );
            }
            else if( is_array( $cookies ) )
            {
                foreach( $cookies as $cookie )
                {
                    if( !( $cookie instanceof Cookie ) )
                    {
                        throw new \UnexpectedValueException( "Each member of the \$cookies array must be a Cookie instance" );
                    }
                    
                    $this->add( $cookie );
                }
            }
            else
            {
                throw new \InvalidArgumentException( "\$cookies argument must be either an array of Cookie instances or a CookieContainer instance, '" . gettype( $cookies ) . "' given" );
            }
        }
        
        /**
         * Returns cookies that match specified criteria
         * 
         * @param string $uri [optional]
         * @param bool $notExpiredOnly [optional]
         * @return array
         */
        public function getCookies( $uri = "", $notExpiredOnly = false )
        {
            $cookies = $this->cookies;
            
            if( !empty( $uri ) )
            {
                // Filter out cookies that don't match $uri
                $cookies = array_filter( $cookies, function( $cookie ) use( $uri ) {
                    return $cookie->matchesUri( $uri );
                } );
            }
            
            if( $notExpiredOnly )
            {
                // Filter out expired cookies
                $cookies = array_filter( $cookies, function( $cookie ) {
                    return !$cookie->isExpired();
                } );
            }
            
            return array_values( $cookies );
        }
        
        /**
         * Returns a HTTP header string that contains cookies that match specified criteria
         * 
         * @param string $uri [optional]
         * @param bool $notExpiredOnly [optional]
         * @return string
         */
        public function getCookieHeader( $uri = "", $notExpiredOnly = false )
        {
            $cookies = $this->getCookies( $uri, $notExpiredOnly );
            array_walk( $cookies, function( &$cookie ) {
                $cookie = (string)$cookie;
            } );
            
            return implode( "; ", $cookies );
        }
        
        /**
         * Removes cookies that match specified criteria from the container
         * 
         * Notice that this method has the '$expiredOnly' parameter, while getCookies
         * and getCookieHeader methods have the '$notExpiredOnly'
         * 
         * @param string $uri [optional]
         * @param bool $expiredOnly [optional]
         */
        public function removeCookies( $uri = "", $expiredOnly = false )
        {
            $cookiesToRemove = $this->getCookies( $uri );
            if( $expiredOnly )
            {
                // Filter out expired cookies
                $cookiesToRemove = array_filter( $cookiesToRemove, function( $cookie ) {
                    return $cookie->isExpired();
                } );
            }
            
            $this->cookies = array_values( array_diff( $this->cookies, $cookiesToRemove ) );
        }
        
        /**
         * Removes all cookies from the container
         */
        public function clear()
        {
            $this->cookies = array();
        }
        
        /**
         * Determines whether this container is empty
         *
         * @return bool
         */
        public function isEmpty()
        {
            return $this->count() == 0;
        }
        
        
        
        /**
         * Returns a number of cookies stored in this container
         * 
         * @return int
         */
        public function count()
        {
            return count( $this->cookies );
        }
        
        /**
         * Returns an iterator for this container
         * 
         * @return \Traversable
         */
        public function getIterator()
        {
            return new \ArrayIterator( $this->cookies );
        }
        
    	/**
         * Returns a string containing all cookies from this container
         * in the "{key}={value}" format with a semicolon between each pair
         * of cookies
         *
         * @return string
         */
        public function __toString()
        {
            return implode( "; ", $this->cookies );
        }
        
        /**
         * Returns a new CookieContainer object filled with cookies
         * extracted from the given header
         * 
         * The header should be a valid HTTP response header
         * 
         * @param string $header
         * @return Curly\Cookies\CookieContainer
         */
        public static function extractCookies( $header )
        {
            preg_match_all( "~^Set-Cookie:.*$~im", $header, $matches );
            $cookies = $matches[0];
            array_walk( $cookies, function( &$cookie ) {
                $cookie = Cookie::parse( $cookie );
            } );
            
            $container = new CookieContainer();
            $container->addMultiple( $cookies );
            return $container;
        }
    }
}